import { Board } from './Board';
import { Piece, PieceColor, PieceType, toPieceType } from './Piece';
import { range } from '../utils/range';
import { Game } from './Game';

const { abs } = Math;

export class Move {
    constructor(row1: number, col1: number, row2: number, col2: number) {
        this.sourceRow = row1;
        this.sourceCol = col1;
        this.targetRow = Math.min(9, Math.max(0, row2));
        this.targetCol = Math.min(8, Math.max(0, col2));
    }

    public static fromOffset(piece: Piece, rowOffset: number, colOffset: number) {
        return new Move(piece.row, piece.col, piece.row + rowOffset, piece.col + colOffset);
    }

    public static deserialize(board: Board, stmt: string): Move {
        stmt = stmt.trim();
        if (stmt.length > 5) {
            throw new Error('Not a valid move notation! ' + stmt);
        }
        let type;
        const color = stmt.charAt(0) < 'a' ? PieceColor.RED : PieceColor.DARK;
        let disambiguation;
        let sourceCol, sourceRow, targetCol, targetRow, direction, offset;
        if (stmt.length === 4) {
            [ type, sourceCol, direction, offset ] = [...stmt];
        } else {
            [ type, disambiguation, sourceCol, direction, offset ] = [...stmt];
        }
        type = toPieceType(type);
        disambiguation = Number(disambiguation) || 1;
        sourceCol = Number(sourceCol);
        offset = Number(offset);
        let iter;
        if (color === PieceColor.RED) {
            iter = range(0, 9);
        } else {
            iter = range(9, 0);
        }
        for (let i = 0; i < iter.length; i++) {
            const piece = board.getPieceAt(iter[i], sourceCol);
            if (piece.type === type && piece.color === color) {
                if (disambiguation === 1) {
                    sourceRow = iter[i];
                    break;
                } else {
                    --disambiguation;
                }
            }
        }
        if (sourceRow == null) {
            throw new Error('There is no such a piece!');
        }
        switch (type) {
            case PieceType.PAWN:
            case PieceType.CANNON:
            case PieceType.ROOK:
            case PieceType.GENERAL:
                switch (direction) {
                    case '+':
                        targetCol = sourceCol;
                        targetRow = sourceRow + (color === PieceColor.RED ? -offset : offset);
                        break;
                    case '-':
                        targetCol = sourceCol;
                        targetRow = sourceRow - (color === PieceColor.RED ? -offset : offset);
                        break;
                    case '=':
                        targetRow = sourceRow;
                        targetCol = offset;
                        break;
                    default:
                        throw new Error('Invalid direction notation!');
                }
                break;
            case PieceType.KNIGHT:
                switch (direction) {
                    case '+':
                        targetCol = offset;
                        targetRow = sourceRow + (color === PieceColor.RED ?
                            (abs(targetCol - sourceCol) === 1 ? -2 : -1)
                            : (abs(targetCol - sourceCol) === 1 ? 2 : 1));
                        break;
                    case '-':
                        targetCol = offset;
                        targetRow = sourceRow - (color === PieceColor.RED ?
                            (abs(targetCol - sourceCol) === 1 ? -2 : -1)
                            : (abs(targetCol - sourceCol) === 1 ? 2 : 1));
                        break;
                    case '=':
                        throw new Error('Knight cannot make such a move!');
                    default:
                        throw new Error('Invalid direction notation!');
                }
                break;
            case PieceType.BISHOP:
                switch (direction) {
                    case '+':
                        targetCol = offset;
                        targetRow = sourceRow + (color === PieceColor.RED ? -2 : 2);
                        break;
                    case '-':
                        targetCol = offset;
                        targetRow = sourceRow - (color === PieceColor.RED ? -2 : 2);
                        break;
                    case '=':
                        throw new Error('Bishop cannot make such a move!');
                    default:
                        throw new Error('Invalid direction notation!');
                }
                break;
            case PieceType.ADVISOR:
                switch (direction) {
                    case '+':
                        targetCol = offset;
                        targetRow = sourceRow + (color === PieceColor.RED ? -1 : 1);
                        break;
                    case '-':
                        targetCol = offset;
                        targetRow = sourceRow - (color === PieceColor.RED ? -1 : 1);
                        break;
                    case '=':
                        throw new Error('Advisor cannot make such a move!');
                    default:
                        throw new Error('Invalid direction notation!');
                }
                break;
            default:
                throw new Error('Invalid piece notation');
        }
        return new Move(sourceRow, sourceCol, targetRow, targetCol);
    }

    public static applyMove(game: Game, stmt: string): boolean {
        const board = game.board;
        const move = Move.deserialize(board, stmt);
        return game.makeMove(move);
    }

    public getSourcePiece(board: Board) {
        return board.getPieceAt(this.sourceRow, this.sourceCol);
    }

    public getTargetPiece(board: Board) {
        return board.getPieceAt(this.targetRow, this.targetCol);
    }

    public serialize(board: Board): string {
        const piece = board.getPieceAt(this.sourceRow, this.sourceCol);
        const type = piece.toString();
        // find vertically for duplicate piece disambiguation
        let disambiguation = 1;
        let iter;
        if (piece.color === PieceColor.RED) {
            iter = range(0, 9);
        } else {
            iter = range(9, 0);
        }
        for (let i = 0; i < iter.length; i++) {
            if (iter[i] === this.sourceRow) {
                break;
            }
            const currentPiece = board.getPieceAt(iter[i], this.sourceCol)
            if (currentPiece.type === piece.type && currentPiece.color === piece.color) {
                ++disambiguation;
            }
        }
        const source = this.sourceCol;
        // find direction
        let direction;
        if (piece.color === PieceColor.RED) {
            if (this.targetRow < this.sourceRow) {
                direction = '+';
            } else if (this.targetRow > this.sourceRow) {
                direction = '-';
            } else {
                direction = '=';
            }
        } else {
            if (this.targetRow < this.sourceRow) {
                direction = '-';
            } else if (this.targetRow > this.sourceRow) {
                direction = '+';
            } else {
                direction = '=';
            }
        }
        let offset;
        // find offset modifier
        switch (piece.type) {
            case PieceType.ROOK:
            case PieceType.PAWN:
            case PieceType.CANNON:
            case PieceType.GENERAL:
                if (this.targetRow - this.sourceRow === 0) {
                    offset = this.targetCol;
                } else if (this.targetCol - this.targetCol === 0) {
                    offset = Math.abs(this.targetRow - this.sourceRow);
                } else {
                    offset = this.targetCol;
                }
                break;
            case PieceType.KNIGHT:
            case PieceType.BISHOP:
            case PieceType.ADVISOR:
                offset = this.targetCol;
                break;
        }

        return `${type}${disambiguation > 1 ? disambiguation : ''}${source}${direction}${offset}`;
    }

    public readonly sourceRow: number;
    public readonly sourceCol: number;
    public readonly targetRow: number;
    public readonly targetCol: number;
}
